Vlakna - zacatky

Otázka od: Ing. Jiri Sokol

3. 9. 2004 11:24

Ahoj panove!
Muj problem je nasledujici. Prebral jsem aplikaci, nechtelo se mi ji predelavat
odznova a ted porad narazim na nejake problemy.
Co bych momentalne potreboval vyresit je tohle:
- hlavni aplikace slouzici k obsluze zprav, zobrazovani nejkych dilcich udaju
atd.
- jeden nezavisly thread - ktery se bude spoustet v nejakem casovem intervalu -
sam si zjisti jestli ma neco udelat provede to - do te doby se nesmi spustit
dalsi thread...
- druhy thread - opet bude spusten po nejakem casovem intervalu - plati to
same, co v prvnim pripade - nemusi delat nic, ale muzou mu operace zabrat i
nekolik sekund - nesmi dojit k opetovnemu spusteni dalsiho threadu

to, ze nesmi dojit k opetovnemu spusteni jednotlivych threadu plati i v
kombinaci mezi nimi - zkratka, kdyz uz nejaky thread bezi, nesmi bezet dalsi.

Muzete mi,prosim, pomoct vytvorit primitivni zakladni aplikaci, ktera bude
splnovat vyse uvedene? Muzem si odpoustit deklaraci fromu atd., ale zajimalo by
me, kdy vytvorit thread, jestli mam volat execute z nejakeho timeru, co na to
synchronizace atd.
Verim, ze lidem, co budou po me zacinat s thready to hodne pomuze.

Vlakno1, Vlakno2: TThread;

procedure TForm.Create(Sender:TObject);
begin
  Vlakno1:=TThread.Create(Application.Handle);
  Valkno2:=TThread.Create(Application.Handle);
end;

Procedure TForm.Timer1OnTimer(Sender: TObject);
begin
  if nebezi vlakno2 a nebezi ani vlakno 1 then
    Vlakno1.Execute;
end;

Procedure TForm.Timer1OnTimer(Sender: TObject);
begin
  if nebezi vlakno2 a nebezi ani vlakno 1 then
    Vlakno2.Execute;
end;

procedure TForm.Destroy(Sender: TObject);
begin
  Vlakno1.Terminate;
  Vlakno1.WaitFor;
  Vlakno2.Terminate;
  Vlakno2.WaitFor;
end;

je to OK?
a co dal? co se dela v metode Create toho vlakna? Jak se synchronizuje -
potreboval bych z toho vlakna hazet neco do Edit1.Text na Form. V Create mam
vytvorit samotnou TIBDatabase + TIBTransaction + TIBQuery? Muzu nejak kopirovat
nastaveni hodnot z komponent, ktere to uz maji nastavene v DataModulu? Jak?
(mam namysli v souvislosti se synchronizaci - pochopitelne, ze normalne - mimo
vlakno - to umim)
Musim si dat jeste bacha na neco?
Diky moc vsem a predem!
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater

Odpovedá: Jiri Virt

3. 9. 2004 12:22

Tohle je v helpu D5. mohlo by to byt to co potrebujes?

unit Pg1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls, Pg2;

const
  WM_ThreadDoneMsg = WM_User + 8;

type
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    ProgressBar2: TProgressBar;
    Button1: TButton;
    Button2: TButton;
    TrackBar1: TTrackBar;
    TrackBar2: TTrackBar;
    Bevel1: TBevel;
    Bevel2: TBevel;
    Label1: TLabel;

    Label2: TLabel;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure TrackBar2Change(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

  private
    { Private declarations }
    MyThread1 : TMyThread; // thread number 1
    MyThread2 : TMyThread; // thread number 2
    Thread1Active : boolean; // used to test if thread 1 is active
    Thread2Active : boolean; // used to test if thread 2 is active
    procedure ThreadDone(var AMessage : TMessage); message WM_ThreadDoneMsg;
// Message to be sent back from thread when its done
  public
    { Public declarations }

  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject); // Create Thread 1
{ The thread will destroy iteself when it is done executing because
FreeOnTerminate is set to true.
The first paramter is the priority, and the second is the progressbar to
update.
}
begin
   if (MyThread1 = nil) or (Thread1Active = false) then // make sure its not
already running

   begin
     MyThread1 := TMyThread.CreateIt(TrackBar1.Position, ProgressBar1);
     Thread1Active := true;
   end
   else
     ShowMessage('Thread still executing');
end;

procedure TForm1.Button2Click(Sender: TObject); // Create Thread 2
begin
   if (MyThread2 = nil) or (Thread2Active = false) then // make sure its
not already running
   begin
     MyThread2 := TMyThread.CreateIt(TrackBar2.Position, ProgressBar2);

     Thread2Active := true;
   end
   else
     ShowMessage('Thread still executing');
end;

procedure TForm1.Button3Click(Sender: TObject); // Terminate Thread 1
begin
  if (MyThread1 <> nil) and (Thread1Active = true) then // check to see if
it is running
    MyThread1.Terminate
  else
   ShowMessage('Thread not started');
end;

procedure TForm1.Button4Click(Sender: TObject); // Terminate Thread 2

begin
  if (MyThread2 <> nil) and (Thread2Active = true) then // check to see if
it is running
    MyThread2.Terminate
  else
    ShowMessage('Thread not started');
end;

procedure TForm1.ThreadDone(var AMessage: TMessage); // keep track of when
and which thread is done executing
begin
  if ((MyThread1 <> nil) and (MyThread1.ThreadID =
cardinal(AMessage.WParam))) then

  begin
      Thread1Active := false;
  end;
  if ((MyThread2 <> nil) and (MyThread2.ThreadID =
cardinal(AMessage.WParam))) then
  begin
      Thread2Active := false;
  end;
end;


procedure TForm1.FormCreate(Sender: TObject); // initialize to zero
begin
  Thread1Active := false;
  Thread2Active := false;
end;


procedure TForm1.TrackBar1Change(Sender: TObject); // set Thread 1 Priority

begin
  if (MyThread1 <> nil) and (Thread1Active = true) then
     MyThread1.priority := TThreadPriority(TrackBar1.Position);
end;

procedure TForm1.TrackBar2Change(Sender: TObject); // set Thread 2 Priority
begin
  if (MyThread2 <> nil) and (Thread2Active = true) then
    MyThread2.priority := TThreadPriority(TrackBar2.Position);
end;


procedure TForm1.FormDestroy(Sender: TObject); // Terminate any threads
still running

begin
   if (MyThread1 <> nil) and (Thread1Active = true) then
   begin
     MyThread1.Terminate;
     MyThread1.WaitFor; // wait for it to terminate
   end;
   if (MyThread2 <> nil) and (Thread2Active = true) then
   begin
     MyThread2.Terminate;
     MyThread2.WaitFor;
   end;
end;

end.


Odpovedá: Ing. Jiri Sokol

3. 9. 2004 12:31

> [mailto:delphi-l-owner@clexpert.cz] On Behalf Of Jiri Virt
> Sent: Friday, September 03, 2004 12:14 PM
>
> Tohle je v helpu D5. mohlo by to byt to co potrebujes?
>

Ahoj.
Je to asi z vetsi casti to co potrebuju. Nicmene tam nebyly jeste odpovedi na
otazky synchronizace a dalsi:
- co se dela v metode Create toho vlakna?
- jak se spravne synchronizuje - potreboval bych z toho vlakna hazet neco do
Edit1.Text na Form - metodu synchronize volam presne kdy?
- v Create vlakna mam vytvorit samotnou TIBDatabase + TIBTransaction +
TIBQuery? Muzu nejak kopirovat nastaveni hodnot z komponent, ktere to uz maji
nastavene v DataModulu? Jak?
- jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak bezi jako
klasicky kod a nebo po dobehnuti do konce jede aut. od zacatku? (to je asi
blbost, co?)

- musim si dat jeste bacha na neco?

Diky
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater


Odpovedá: Jiri Virt

3. 9. 2004 12:39

 
>- co se dela v metode Create toho vlakna?

var

  SecondProcess TMyThread; { TMyThread is a custom descendant of TThread }
begin
  SecondProcess := TMyThread.Create(True); { create suspended -
secondprocess does not run yet }
  SecondProcess.Priority = tpLower; { set the priority to lower than normal
}
  SecondProcess.Resume; { now run the thread }
end;

> - jak se spravne synchronizuje - potreboval bych z toho vlakna hazet neco
do Edit1.Text na Form - metodu synchronize volam presne kdy?

procedure TMyThread.PushTheButton;

begin
  Button1.Click();
end;

procedure TMyThread.Execute;
begin
...
  Synchronize(PushTheButton);
  ...
end;


>- v Create vlakna mam vytvorit samotnou TIBDatabase + TIBTransaction +
TIBQuery? Muzu nejak kopirovat nastaveni hodnot z komponent, ktere to uz
maji nastavene v DataModulu? Jak?

Muzes zkopirovat

procedure TMyThread.Execute;
begin
...
 IBThDatabase := MyIBDatabase;
  ...
end;


>- jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak bezi
jako klasicky kod a nebo po dobehnuti do konce jede aut. od zacatku? (to je
asi blbost, co?)


Ne .. Dojede a pokud to nemas v while nebo for cyklu, tak se I thread ukonci



Vsech to mam vytahnute z helpu, mrkni tam, je tam u kazdeho vyrazu k threadu
ukazka (exampl) a pekne vysvetleno

Jirka Virt


Odpovedá: Petr Fejfar

3. 9. 2004 12:13

Ing. Jiri Sokol wrote:

> ale zajimalo by me, kdy vytvorit thread, jestli mam volat execute z
> nejakeho timeru, co na to synchronizace atd.
> Verim, ze lidem, co budou po me zacinat s thready to hodne pomuze.

Nejdriv by to chtelo upresnit zadani: pises, co se nesmi, ale nepises, co
se ma  

> if nebezi vlakno2 a nebezi ani vlakno 1 then
> Vlakno1.Execute;

Kdyz vezmu tuhle podminku, tak sice rika, ze thready nepobezi soucase,
ale na druhou stranu se muze stat, ze perioda spousteni threadu muze znacne
"kulhat" - hypoteticky se nektery ze threadu nemusi spoustet vubec. Takze je
nutne specifikovat, jestli ti to tak staci nebo jestli pozadujes, aby
jakmile jeden thread skonci se spustil ten druhy, i kdyz se zpozdenim apod.

> Vlakno1.Execute;

Tohle je jedna z hrubek, ktera se casto dela - kdybys to napsal takto, tak
ti kod v metode Execute stale pobezi v contextu volajiciho tj. hlavniho
threadu.

Thread se nespousti volanim jeho metody execute, ale rozebehne se bud
automaticky, pokud konstruktoru nedas parametr CreateSuspended a nebo
explicitne volanim metody Resume.

> jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak
> bezi jako klasicky kod a nebo po dobehnuti do konce jede aut. od
> zacatku? (to je asi blbost, co?)

To zalezi prave na zadani - casto se thread vytvori a spusti a pak v tele
metody Execute ceka pomoci funkce WaitForSingleObject/WaitForMultipleObjects
na stav nejakeho synchronizacniho prostredku (event, semaphore apod), ktery
ho "vzbudi", thread udela co ma a vrati se do stavu cekani - tedy obsahuje
cyklus. Takto se typicky resi akce, ktere se periodicky opakuji.
Jednorazeove ulohy se zpravidla resi vytvorenim threadu a jeho zrusenim po
provedeni ulohy.


HTH, pf



Odpovedá: Ing. Jiri Sokol

3. 9. 2004 12:46

> Od: Petr Fejfar <development@callnet.cz>
> Datum: 03.09.2004 13:14:45

> > if nebezi vlakno2 a nebezi ani vlakno 1 then
> > Vlakno1.Execute;
>
> Kdyz vezmu tuhle podminku, tak sice rika, ze thready nepobezi soucase,
> ale na druhou stranu se muze stat, ze perioda spousteni threadu muze znacne
> "kulhat" - hypoteticky se nektery ze threadu nemusi spoustet vubec. Takze je
> nutne specifikovat, jestli ti to tak staci nebo jestli pozadujes, aby
> jakmile jeden thread skonci se spustil ten druhy, i kdyz se zpozdenim apod.

Na tom je hodne pravdy -   budu si asi muset udelat nejakou "frontu"
udalosti... aby nebezelo jenom jedno to vlakno...
 
> To zalezi prave na zadani - casto se thread vytvori a spusti a pak v tele
> metody Execute ceka pomoci funkce WaitForSingleObject/WaitForMultipleObjects
> na stav nejakeho synchronizacniho prostredku (event, semaphore apod), ktery
> ho "vzbudi", thread udela co ma a vrati se do stavu cekani - tedy obsahuje
> cyklus. Takto se typicky resi akce, ktere se periodicky opakuji.

Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek? No snda
nechci moc... nevim... Stejne diky aspon za nakopnuti, timhle se budu chtit
ubirat...
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater

Odpovedá: Ondrej Kelle

3. 9. 2004 12:49

Borland pred nejakou dobou vyhlasil sutaz o multithreaded aplikaciach,
vysledky tej sutaze najdes tu:
http://bdn.borland.com/article/0,1410,29786,00.html
Myslim, ze je to celkom dobry material na studium.

Jeden z ucastnikov napisal aj clanok, kde svoj (vitazny) prispevok popisuje:
http://www.thedelphimagazine.com/samples/1712/1712.htm
Je v nom zaujimave to, ako na updatovanie VCL v hlavnom threade nepouziva
Synchronize, ale PostMessage.

HTH
TOndrej

Odpovedá: Petr Fejfar

3. 9. 2004 12:50

Ing. Jiri Sokol wrote:

> Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek?
> No snda nechci moc... nevim... Stejne diky aspon za nakopnuti, timhle
> se budu chtit ubirat... Jirka

Kdyz tak vecer (v noci), ted musim odjet.

pf


Odpovedá: Petr Fejfar

3. 9. 2004 23:13

Ing. Jiri Sokol wrote:

> Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek?

Vidim, ze se k tomu nikdo nehlasi:

1. Pro tvuj problem bych volil thread, ktery bude cekat na zadost o
zpracovani.

- zadost bych implementoval semaphorem, ktery by byl atributem threadu
  (semafor resi tu "frontu" pozadavku)
- exklusivitu behu threadu bych resil mutexem - protoze se nekdy muze
    vyskytnout pozadavak na exklusivitu vzhledem k jine aplikaci
    (treba k instalacnimu scriptu), pouzil bych obecnejsi pojmenovany mutex
    jehoz jmeno bych predaval jako argument konstruktoru.
- Thread bych prerusoval/ukoncoval "ciste" pomoci dedikovane event,
    ktera by byla atributem threadu

2. Protoze mas dva ruzne thready, ktere ale budou mit radu spolecnych
vlastnosti,
    je na miste zapremyslet o taxonomii threadu, asi nejjednodussi by byla:

    - TAbstractThread(TThread)
        - TExlusiveThread(TAbstractThread)
        - TIndependentThread(TAbstractThread)

Nastinil jsem ti skelet threadu v duchu, v jakem thready pouzivame, ale
zamerne jsem sloucil podstatne vlastnosti z hierarchie trid do jedine tridy.
Sam si to uz jiste rozhodis do trid podle vlastnich potreb.

3. V aplikaci vytvoris instanci threadu a zase ji explicitne zrusis
(preferuji tohle reseni s plnou kontrolou zivota threadu, protoze pri
automatickem ruseni threadu po dobehnuti metody Execute ti zbyde v nejake
promenne reference na neplatny objekt, coz muze vest k docela neprijemnym
chybam. Rovnez tak automaticke rozebehnuti threadu muze cinit zaludne
problemy). Priklad:

const
  cMTX_ExlusiveThread =
'..._worldwide_unique_mutex_name_as_global_literal...';

  Thread1 := TExclusiveThread.Create(cMTX_ExlusiveThread);
  Thread1.Resume;
  ...
  Thread1.NewRequest;
  ...
  Thread1.Kill(INFINITE);
  Thread1.Free;


4. Nize je skelet - psal jsem ho z hlavy, takze jsem se mohl nekde upsat,
ale podstatne chyby by v tom snad byt nemely. Vsimni si
inicializace/finalizace
threadu, ktera bezi jiz v kontextu vlastniho threadu a metody Kill,
ktera slouzi k regulernimu ukonceni threadu (kdyby Borland udelal metodu
Terminate virtualni, overridnula by se ta, ovsem neudelal, takze je nutne to
delat jinak). Cekani je dvoji: prvni WaitFor.... bez timeoutu ceka na novy
pozadavek, druhe vnorene volani na mutex s timeoutem tj. dobu, do kdy
museji skoncit ostatni thready a musi se zahajit zpracovani pozadavku. Psane
je to tak, aby se snadno daly pridavat dalsi handles, na ktere ceka:

const
  maxExclusivityTimeout = 60*1000; // max. time [ms] the mutex has to be
grabbed

type
  eWaitHandles = (ewhKill,ewhRequest);
  eGrabHandles = (eghKill,eghExclusivity);
type
  xInternal = class(Exception);
  xFatal = class(Exception);

type
  TExclusiveThread = class(TThread)
  private
    procedure __Body;
    procedure __ProcessRequest;
  protected
    FRunning: boolean;
    FKillEvent: THandle;
    FWaitHandles: array[eWaitHandles] of THandle;
    FGrabHandles: array[eGrabHandles] of THandle;
    FMutexName: ANSIString;
  protected
    procedure _Init; virtual;
    procedure _Done; virtual;
    function _GrabMutex(ATimeout:dword): boolean;
  public
    constructor Create(const AMutexName:ANSIString);
    procedure Execute; override;
    procedure NewRequest;
    function Kill(ATimeout:dword): boolean;
  public
    property Running: boolean read FRunning;
  end;

constructor TExclusiveThread.Create(const AMutexName:ANSIString);
begin
  inherited Create(TRUE);
  FreeOnTerminate := FALSE;
  FMutexName := AMutexName;
end;

procedure TExclusiveThread.Execute;
begin
  _Init;
  FRunning := TRUE;
  try
      while not Terminated do
      try
        __Body;
      except
        on E:xFatal do
          raise;
      end;
  finally
    FRunning := FALSE;
    _Done;
  end;
end;

procedure TExclusiveThread.NewRequest;
begin
  if FWaitHandles[ewhRequest]<>0 then
    ReleaseSemaphore(FWaitHandles[ewhRequest],1,nil);
end;

function TExclusiveThread.Kill(ATimeout:dword): boolean;
begin
  if FKillEvent<>0 then
    SetEvent(FKillEvent);
  Terminate;
  case WaitForSingleObject(Handle,ATimeout) of
    WAIT_TIMEOUT: Result := FALSE;
    else Result := TRUE;
  end;
end;

procedure TExclusiveThread._Init;
begin
  FKillEvent := CreateEvent(nil,bManualReset,bNonSignaled,'');
  if FKillEvent=0 then
    RaiseLastOSError;
  //
  FWaitHandles[ewhKill] := FKillEvent;
  FWaitHandles[ewhRequest] := CreateSemaphore(nil,0,maxint,'');
  if FWaitHandles[ewhRequest]=0 then
    RaiseLastOSError;
  //
  FGrabHandles[eghKill] := FKillEvent;
  FGrabHandles[eghExclusivity] := CreateMutex(nil,FALSE,pChar(FMutexName));
  if FGrabHandles[eghExclusivity]=0 then
    RaiseLastOSError;
end;

procedure TExclusiveThread.__Body;
var
  WxRes: dword;
begin
  WxRes :=
WaitForMultipleObjects(ord(high(FWaitHandles))-ord(low(FWaitHandles))+1,addr
(FWaitHandles),FALSE,INFINITE);
  case WxRes of
    WAIT_FAILED:
      begin
        RaiseLastOSError;
      end;
    WAIT_TIMEOUT:
      begin
        raise xInternal.Create('....'); // may not occur
      end;
    WAIT_OBJECT_0+ord(ewhKill):
      begin
        exit;
      end;
    WAIT_OBJECT_0+ord(ewhRequest):
      begin
        if _GrabMutex(maxExclusivityTimeout) then
          try
            __ProcessRequest;
          finally
            Win32Check(ReleaseMutex(FGrabHandles[eghExclusivity]));
          end
        else if not Terminated then
          raise xInternal.Create('.....');
      end;
    WAIT_ABANDONED_0..WAIT_ABANDONED_0+ord(high(FWaitHandles)):
      raise xInternal.CreateFmt('....',[WxRes-WAIT_ABANDONED_0]);
    else
      raise xInternal.CreateFmt('....',[WxRes]);
  end;
end;

function TExclusiveThread._GrabMutex(ATimeout:dword): boolean;
var
  WxRes: dword;
begin
  Result := FALSE;
  WxRes :=
WaitForMultipleObjects(ord(high(FGrabHandles))-ord(low(FGrabHandles))+1,addr
(FGrabHandles),FALSE,ATimeout);
  case WxRes of
    WAIT_FAILED:
      RaiseLastOSError;
    WAIT_TIMEOUT:
      ;
    WAIT_OBJECT_0+ord(eghKill):
      ;
    WAIT_OBJECT_0+ord(eghExclusivity):
      Result := TRUE;
    WAIT_ABANDONED_0..WAIT_ABANDONED_0+ord(high(FGrabHandles)):
      raise xInternal.CreateFmt('....',[WxRes-WAIT_ABANDONED_0]);
    else
      raise xInternal.CreateFmt('...');[WxRes]);
  end;
end;


procedure TExclusiveThread._Done;
begin
  if FKillEvent<>0 then
    CloseHandle(FKillEvent);
  //
  if FWaitHandles[ewhRequest]<>0 then
    CloseHandle(FWaitHandles[ewhRequest]);
  //
  if FGrabHandles[eghExclusivity]<>0 then
    CloseHandle(FGrabHandles[eghExclusivity]);
end;

procedure TExclusiveThread.__ProcessRequest;
begin
end;


HTH, pf